"""
Fisher线性判别器
"""
import numpy as np
from singlelayerneuralnetwork import SingleLayerNeuralNetwork

class Fisher(SingleLayerNeuralNetwork):
    """
    Fisher线性判别器
    使用 model = Fisher() 定义示例
    使用 model.fit(Xtrain, Ytrain) 训练样本
    使用 model.predict(Xtest) 进行测试
    """
    
    def __init__(self):
        super().__init__(iteration=None, randomstate=None)
        # 投影面上分界点的取值
        self.segmentation = None


    def fit(self, Xtrain, Ytrain):
        '''
        训练测试样本
        Xtrain： 训练样本的数据，N * d 矩阵，第 i 行表示第 i 个数据
        Ytrain： 训练样本的标签，N * 1 矩阵，第 i 行表示第 i 个标签
        @return：Fisher线性判别器在训练集上预测的错误率
        '''
        # 训练样本的维数D和个数N
        N, D = Xtrain.shape

        # 按照训练样本标签对训练样本进行分类
        pos = np.array([])
        neg = np.array([])
        for i in range(N):
            # 统计正类
            if Ytrain[i,0] > 0:
                if len(pos) == 0:
                    pos = Xtrain[i,:].reshape(1, D)
                else:
                    pos = np.append(pos, Xtrain[i,:].reshape(1, D), 0)

            # 统计负类
            if Ytrain[i,0] < 0:
                if len(neg) == 0:
                    neg = Xtrain[i,:].reshape(1, D)
                else:
                    neg = np.append(neg, Xtrain[i,:].reshape(1, D), 0)

        # 计算最佳投影面
        mean_pos = np.sum(pos, axis=0).reshape(1, D) / len(pos)
        mean_neg = np.sum(neg, axis=0).reshape(1, D) / len(neg)
        var_pos = np.matmul(np.transpose(pos - mean_pos), pos - mean_pos)
        var_neg = np.matmul(np.transpose(neg - mean_neg), neg - mean_neg)
        W = np.matmul(np.linalg.inv(var_pos + var_neg), np.transpose(mean_pos - mean_neg))

        # 计算分界点取值
        segmentation = (np.matmul(mean_pos + mean_neg, W) / 2)[0,0]

        # 计算分类结果在训练集上的错误率
        loss = 0
        for i in range(N):
            x = Xtrain[i,:].reshape(1, D)
            y = Ytrain[i,0]
            y_pred = np.matmul(x, W)[0,0]
            if (y_pred > segmentation and y == 1) or (y_pred < segmentation and y == -1):
                continue
            else:
                loss += 1
        loss = loss / N

        # 保存计算结果
        self.W = W.reshape(D)
        self.w_0 = -segmentation
        self.dimen = D
        self.Xtrain = Xtrain
        self.Ytrain = Ytrain
        self.train_score = loss
        self.segmentation = segmentation
        
        return self.train_score


    def predict(self, Xtest):
        '''
        对测试样本进行预测
        Xtest：    测试样本的数据，N * d 矩阵，第 i 行表示第 i 个数据
        dichotomy：输出值表示二分类结果(True)
        @return：  测试样本的预测结果，N * 1 矩阵，第 i 行表示第 i 个标签
        '''
        # 检测初始条件是否满足
        super().predict(Xtest)

        # 获取预测样本的维数D和样本数量N
        N, D = Xtest.shape
        W = np.transpose(self.W.reshape(1, D))
        w_0 = self.w_0

        # 进行预测
        y_predict = np.zeros((N, 1))
        for i in range(N):
            x = Xtest[i, :].reshape(1, D)
            y = np.matmul(x, W)[0,0] + w_0
            if y > 0:
                y_predict[i, 0] = 1
            elif y < 0:
                y_predict[i, 0] = -1
            else:
                y_predict[i, 0] = 0

        # 保存测试数据集和测试结果集
        self.Xtest = Xtest
        self.Ypred = y_predict

        return self.Ypred


    def getTrainResult(self):
        '''
        获取训练结果
        @return：  训练样本上的得分
        '''
        return self.train_score


    def getW(self):
        '''
        获取分类面参数 w_0--w_i
        @return：元组类型，例如 (w_0, W)
                 第一个元素表示投影面的w_0
                 第二个元素表示投影面的W
                 第三个元素表示分界点的值
        '''
        return (self.w_0, self.W, self.segmentation)


if __name__ == "__main__":
    X_train = np.array(
        [[1,2],[5,7],[1,1],[8,5],[4,1],
         [3,1],[4,9],[3,10],[3,2],[2,1],
         [8,8],[7,9],[2,0],[0,0],[6,11],
         [2,9],[4,2],[3,9],[6,0],[6,9]]
    )
    Y_train = np.array(
        [[1],[-1],[1],[-1],[1],
         [1],[-1],[-1],[1],[1],
         [-1],[-1],[1],[1],[-1],
         [-1],[1],[-1],[1],[-1]]
    )
    X_test = np.array([[0.5,0.5],[4,8],[3,10],[5,6]])

    model = Fisher()
    score = model.fit(X_train, Y_train)
    y_pred = model.predict(X_test)
    w_0, W, segmentation = model.getW()

    print(("Score = {0}\n" + 
          "W = {1}\n" + 
          "w_0 = {2}\n" + 
          "Segmentation = {3}")
          .format(score, W, w_0, segmentation))
    print("y_pred = \n" + str(y_pred))
    model.drawTwoDimenPic(testdata=True)